package com.bkhech.netty.websocket;


import cn.hutool.core.util.StrUtil;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.bkhech.enums.MsgTypeEnum;
import com.bkhech.grace.result.GraceJSONResult;
import com.bkhech.netty.nacos.NacosClient;
import com.bkhech.netty.strategy.MessageHandlerStrategy;
import com.bkhech.netty.utils.JedisPoolUtil;
import com.bkhech.netty.utils.PropertiesUtil;
import com.bkhech.netty.utils.ZKUtil;
import com.bkhech.pojo.netty.ChatMsg;
import com.bkhech.pojo.netty.DataContent;
import com.bkhech.pojo.netty.NettyServerNode;
import com.bkhech.utils.JsonUtils;
import com.bkhech.utils.OkHttpUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Consumer;

/**
 * ChatHandler
 *  聊天消息处理器
 *  TextWebSocketFrame 用于为websocket专门处理的文本数据对象
 * @author guowm
 * @date 2024-11-15
 */
@Slf4j
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private static final ChannelGroup clients = ClientChannelGroup.getInstance();
    private static final Properties properties = PropertiesUtil.getInstance();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 获得客户端传输过来的消息
        String content = msg.text();
        // 获取 Channel
        Channel currentChannel = ctx.channel();
        // 获取 Channel 的长 id 作为用户的唯一标识
        String currentChannelId = currentChannel.id().asLongText();
        log.info("接收到的数据：{}", content);
        // 解析客户端发过来的消息
        DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
        assert dataContent != null;
        dataContent.setExtend(currentChannelId);
        ChatMsg chatMsg = dataContent.getChatMsg();
        // 时间校准，以服务器的时间为准
        chatMsg.setChatTime(LocalDateTime.now());

        // 判断是不是互为黑名单好友，如果是则禁止发送
        // String friendId1st, String friendId2nd
        if (!Objects.equals(chatMsg.getMsgType(), MsgTypeEnum.CONNECT_INIT.type)) {
            try {
                final Instance instance = NacosClient.getNamingService().selectOneHealthyInstance("main-service");
                String isBlackUrl = String.format(properties.getProperty("chat.friend.isBlack.url"), instance.getIp(), instance.getPort());
                String url = StrUtil.format("{}?friendId1st={}&friendId2nd={}", isBlackUrl, chatMsg.getSenderId(), chatMsg.getReceiverId());
                log.info("远程黑名单接口是：{}", url);
                GraceJSONResult<Object> graceJsonResult = OkHttpUtil.get(url);
                if (graceJsonResult.getSuccess()) {
                    Boolean friendlyFlag = (Boolean) graceJsonResult.getData();
                    log.info("远程调用黑名单接口成功，用户1：{}, 用户2：{},是否为好友：{}", chatMsg.getReceiverId(), chatMsg.getSenderId(), friendlyFlag);
                    if (friendlyFlag) {
                        return;
                    }
                }
            } catch (Exception e) {
                log.error("远程调用黑名单接口异常，异常信息为：{}", e.getMessage());
                return;
            }
        }

        // 判断消息类型，处理不同的业务
        Map<String, Object> dataContentMap = new HashMap<>(4) {{
            put("dataContent", dataContent);
            put("channel", currentChannel);

        }};

        // 处理不同类型的消息
        MessageHandlerStrategy.handleMsgByType(dataContentMap);
    }

    /**
     * 客户端连接到服务端之后（打开连接）
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel currentChannel = ctx.channel();
        String currentChannelId = currentChannel.id().asLongText();
        log.info("客户端建立连接，channel对应的长id为: {}", currentChannelId);
        // 获得客户端的 channel，存入到 ChannelGroup 中进行管理（作为一个客户端群组）
        clients.add(currentChannel);
    }

    /**
     * 异常发生并且捕获，移除 Channel
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel currentChannel = ctx.channel();
        String currentChannelId = currentChannel.id().asLongText();
        log.info("服务端发生异常，channel对应的长id为: {}", currentChannelId);
        cause.printStackTrace();
        log.error("异常信息：{}", cause.getMessage());
        // 移除多余的会话
        String userId = UserChannelSession.getUserIdByChannelId(currentChannelId);
        UserChannelSession.removeUselessChannels(userId, currentChannelId);

        // 发生异常之后关闭连接（关闭 Channel）
        currentChannel.close();

        // 从群组（ChannelGroup）中移除对应的 Channel
        clients.remove(currentChannel);
        // 断线之后在 zk 中进行在线人数减少
        this.decrementOnlineCount(userId);
    }

    /**
     * 关闭连接，移除 Channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel currentChannel = ctx.channel();
        String currentChannelId = currentChannel.id().asLongText();
        log.info("客户端关闭连接，channel对应的长id为: {}", currentChannelId);
        // 移除多余的会话
        String userId = UserChannelSession.getUserIdByChannelId(currentChannelId);
        UserChannelSession.removeUselessChannels(userId, currentChannelId);

        // 从群组（ChannelGroup）中移除对应的 Channel
        clients.remove(currentChannel);
        // 断线之后在 zk 中进行在线人数减少
        this.decrementOnlineCount(userId);
    }

    /**
     * 在 zk 中进行在线人数减少
     * @param userId
     */
    private void decrementOnlineCount(String userId) {
        String nettNodeJsonStr = JedisPoolUtil.executeInJedis(jedis -> jedis.get(userId));
        NettyServerNode minNode = JsonUtils.jsonToPojo(nettNodeJsonStr, NettyServerNode.class);
        ZKUtil.decrementOnlineCount(minNode);
    }
}
